package torres.map;

import torres.map.tile.*;
import torres.map.Location;
import torres.item.Item;
import torres.entity.Avatar;
import torres.entity.Entity;
import torres.map.effect.AreaEffect;
import java.io.File;
import java.io.Serializable;

/**
 * @author Torres
 * The 'main' portion of the Model -- represents a two dimensional array of Tiles.
 */
@SuppressWarnings("serial")
public class Map implements Serializable {
	
	private String name;
	private Avatar avatar;
	private String mapName;
	private Tile tiles[][];
	
	/**
	 * Creates a new 100 by 100 Map.
	 */
	public Map() {
		
		tiles = new Tile[100][100];
		avatar = null;
		name = "clopez";
	}
	
	/**
	 * Creates a new Map of the specified size.
	 * @param row The number of rows in the new Map.
	 * @param col The number of columns in the new Map.
	 */
	public Map(int row, int col) {
		
		tiles = new Tile[row][col];
		name = "clopez";
		avatar = null;
		mapName = "";
	}
	
	
	/**
	 * Creates a new Map from the specified file.
	 * @param file The file from which to read in the Map data.
	 */
	public Map(File file) {
		
		tiles = MapGenerator.generate(file);
		name = "clopez";
		mapName = file.getAbsolutePath().toString();
	}
	
	
	/**
	 * Creates a new Map from the specified file.
	 * @param file The file from which to read in the Map data.
	 * @param name The name of this Map.
	 * @param avatar The Avatar to be placed on this Map.
	 */
	public Map(File file, String name, Avatar avatar) {
		
		this(file);
		this.name = name;
		this.avatar = avatar;
		this.mapName = file.getName();
	}
	
	
	/**
	 * Tests to see if the specified Location is a valid location in the map.
	 * @param location The location to test for validity.
	 * @return True if this is a valid Location on the map, false otherwise.
	 */
	public boolean isValid(Location location) {
		
		int x = location.getX();
		int y = location.getY();
		
		if (y >= tiles.length || x >= tiles[0].length || x < 0 || y < 0) {
			return false;			
		}
		
		return true;
	}
	
	/**
	 * Tests to see if the specified Location on the Map is traversable.
	 * @param location The location to test for traversability. 
	 * @return True if the Location is traversable, false otherwise.
	 */
	public boolean isTraversable(Location location) {
		
		return tiles[location.getY()][location.getX()].getTerrain().isTraversable();
	}
	
	
	/**
	 * Add the specified Entity to the specified Location.
	 * @param location The Location to which the Entity is added.
	 * @param entity The Entity to be added.
	 */
	public void addEntity(Location location, Entity entity) {
		
		tiles[location.getY()][location.getX()].addEntity(entity);
	}
	
	/**
	 * Add the specified Item to the specified Location.
	 * @param location The Location to which the Item is added.
	 * @param item The Item to be added.
	 */
	public void addItem(Location location, Item item) {
		
		tiles[location.getY()][location.getX()].addItem(item);
	}
	
	/**
	 * Removes the Entity at the specified Location.
	 * @param location The Location from which the Entity is being removed.
	 */
	public void removeEntity(Location location) {
		
		tiles[location.getY()][location.getX()].removeEntity();
	}
	
	
	/**
	 * Removes the Avatar from the Map.
	 */
	public void removeAvatar() {
		
		tiles[avatar.getLocation().getY()][avatar.getLocation().getX()].removeEntity();
	}
	
	/**
	 * Removes the Item at the specified Location.
	 * @param location The Location from which the Item is being removed.
	 */
	public void removeItem(Location location) {
	
		tiles[location.getY()][location.getX()].removeItem();
	}
	
	
	/**
	 * Returns the Tile corresponding to the specified Location.
	 * @param location The Location from which to retrieve the Tile.
	 * @return The Tile with the same coordinates as the Location.
	 */
	public Tile getTile(Location location) {
		
		return tiles[location.getY()][location.getX()];
	}
	
	/**
	 * Returns the underlying Tiles associated with this Map. 
	 * @return The underlying Tiles associated with this Map.
	 */	
	public Tile[][] getTiles() {
		
		return tiles;
	}
	
	
	/**
	 * Returns the Avatar that is currently on the map.
	 * @return The Avatar that is currently on the map.
	 */
	public Avatar getAvatar() {
		
		return avatar;
	}
	
	
	/**
	 * Sets the Avatar that is currently on the map.
	 * @param avatar The new Avatar for this map.
	 */
	public void setAvatar(Avatar avatar) {
		
		this.avatar = avatar;
	}
	
	/**
	 * Tests to see if the specified Location contains an Obstacle.
	 * @param location The Location to be tested.
	 * @return True if that Location has an Obstacle on it, false otherwise.
	 */
	public boolean hasObstacle(Location location) {
		
		if (tiles[location.getY()][location.getX()].hasItem()) {
			return tiles[location.getY()][location.getX()].getItem().isObstacle();
		}	
		else {
			return false;
		}
	}
	
	/**
	 * Tests to see if there is an Entity at the specified Location.
	 * @param location The Location to be tested.
	 * @return True if that Location has an Entity on it, false otherwise.
	 */
	public boolean hasEntity(Location location) {
	
		return tiles[location.getY()][location.getX()].hasEntity();	
	}
	
	/**
	 * Tests to see if there is an Effect at the specified Location.
	 * @param location The Location to be tested.
	 * @return True if that Location has an Effect on it, false otherwise.
	 */
	public boolean hasEffect(Location location) {
		
		return tiles[location.getY()][location.getX()].hasEffect();
	}
	
	/**
	 * Tests to see if there is an Item at the specified Location.
	 * @param location The Location to be tested.
	 * @return True if that Location has an Item on it, false otherwise.
	 */
	public boolean hasItem(Location location) {
		
		return tiles[location.getY()][location.getX()].hasItem();
	}
	
	
	/**
	 * Returns the Item that is at the specified Location.
	 * @param location The Location from which to retrieve the item.
	 * @return Returns the Item that is at the specified Location if it exists, null otherwise.
	 */
	public Item getItem(Location location) {
		
		return tiles[location.getY()][location.getX()].getItem();
	}
	
	/**
	 * Returns the Effect that is at the specified Location.
	 * @param location The Location from which to retrieve the Effect.
	 * @return Returns the Effect that is at the specified Location if it exists, null otherwise.
	 */
	public AreaEffect getEffect(Location location) {
		
		return tiles[location.getY()][location.getX()].getEffect();
	}
	
	/**
	 * Returns the Terrain that is at the specified Location.
	 * @param location The Location from which to retrieve the Terrain.
	 * @return Returns the Terrain of the specified Location.
	 */
	public Terrain getTerrain(Location location) {
		
		return tiles[location.getY()][location.getX()].getTerrain();
	}
	
	/**
	 * Returns the Entity that is at the specified Location.
	 * @param location The Location from which to retrieve the Entity.
	 * @return Returns the Entity that is at the specified Location if it exists, null otherwise.
	 */
	public Entity getEntity(Location location) {
		
		return tiles[location.getY()][location.getX()].getEntity();
	}
	
	
	/**
	 * Returns the name of this Map.
	 * @return The name of this Map.
	 */
	public String getName() {
		
		return name;
	}
	
	
	/**
	 * Tests to see if the specified Map is equivalent to this Map.
	 * 
	 * Maps are considered equal if they have the same name.
	 * 
	 * @param map The Map to test for equality.
	 * @return True if the maps are the same, false otherwise.
	 */
	public boolean equals(Map map) {
		
		return this.name.equals(map.getName());
	}
	
	/** 
	 * Returns the String representation of this Map.
	 * @return The String representation of this Map.
	 */
	public String toString() {
		return mapName;
	}
	
	/**
	 * Returns the name of this Map without the .map extension.
	 * @return The name of this Map without the .map extension.
	 */
	public String mapName() {
		String[] split = mapName.split("\\.");
		return split[0];
	}
}
